// =======================================================================================
/*!
 * @file    VerInfo.c
 *
 * @brief   Version information functions.
 *
 * @author  Jorge Ramos (jramos at pobox dot com).
 *
 * @version 1.0.0.0 - Aug 06, 2011.
 *          @li Initial version.
 *
 * @remarks Copyright (c) 2011 Jorge Ramos.
 *
 * @remarks This software is provided 'as-is', without any express or implied warranty.
 *          Use it at your own risk. In no event shall the authors or copyright holders
 *          be held liable for any damages arising from the use of this software. Use
 *          and redistribution in source and binary forms, with or without modification,
 *          are permitted. Altered source versions must be plainly marked as such.
 *
 * @remarks This software is distributed in the hope that it will be useful, but WITHOUT
 *          ANY WARRANTY.
 */
// =======================================================================================

#include "Defs.h"
#include "Util.h"
#include "VerInfo.h"

//////////////////////////////////////////////////////////////////////////////////////////

#include <winver.h>

//////////////////////////////////////////////////////////////////////////////////////////

static BOOL m_IsVersionInfoLoaded;

// Version number is formatted as follows: "major.minor.build.private".

static DWORD m_dwProductVersionMS;
static DWORD m_dwProductVersionLS;
static DWORD m_dwFileVersionMS;
static DWORD m_dwFileVersionLS;

static BOOL m_bIsPreRelease;
static BOOL m_bIsSpecialBuild;
static BOOL m_bIsPrivateBuild;

static TCHAR m_sCompanyName [128];
static TCHAR m_sProductName [128];
static TCHAR m_sProductVersion [32];
static TCHAR m_sLegalCopyright [128];
static TCHAR m_sLegalTrademarks [128];
static TCHAR m_sComments [256];
static TCHAR m_sInternalName [128];
static TCHAR m_sOriginalFilename [128];
static TCHAR m_sFileDescription [128];
static TCHAR m_sFileVersion [32];
static TCHAR m_sSpecialBuild [128];
static TCHAR m_sPrivateBuild [128];

//////////////////////////////////////////////////////////////////////////////////////////

static VS_FIXEDFILEINFO* VerQueryFixedFileInfo (LPVOID pVerInfo)
{
    VS_FIXEDFILEINFO* pFileInfo = NULL;
    UINT uInfoLen = 0;

    if (VerQueryValue (pVerInfo, _T ("\\"), (LPVOID*) &pFileInfo, &uInfoLen))
    {
        if (uInfoLen == sizeof (VS_FIXEDFILEINFO))
        {
            if (pFileInfo->dwSignature    == VS_FFI_SIGNATURE &&
                pFileInfo->dwStrucVersion == VS_FFI_STRUCVERSION)
            {
                return pFileInfo;
            }

            Trace (_T ("VerQueryFixedFileInfo: Invalid signature: (dwSignature=%08X, dwStrucVersion=%08X) should be (dwSignature=%08X, dwStrucVersion=%08X)\n"), pFileInfo->dwSignature, pFileInfo->dwStrucVersion, VS_FFI_SIGNATURE, VS_FFI_STRUCVERSION);
        }
        else
        {
            Trace (_T ("VerQueryFixedFileInfo: Invalid length of the version-information value: (uInfoLen=%u) uInfoLen should be %u\n"), uInfoLen, sizeof (VS_FIXEDFILEINFO));
        }
    }
    else
    {
        Trace (_T ("VerQueryFixedFileInfo: VerQueryValue failed\n"));
    }

    return NULL;
}

//////////////////////////////////////////////////////////////////////////////////////////

static LPDWORD VerQueryTranslationInfo (LPVOID pVerInfo, UINT* puXlatCount)
{
    LPDWORD pdwXlatInfo = NULL;
    UINT uInfoLen = 0;

    *puXlatCount = 0;

    if (VerQueryValue (pVerInfo, _T ("\\VarFileInfo\\Translation"), (LPVOID*) &pdwXlatInfo, &uInfoLen))
    {
        if (uInfoLen > 0)
        {
            *puXlatCount = uInfoLen / sizeof (DWORD);
            return pdwXlatInfo;
        }

        Trace (_T ("VerQueryTranslationInfo: Invalid length of the version-information value: (uInfoLen=%u) uInfoLen should be > 0\n"), uInfoLen);
    }
    else
    {
        Trace (_T ("VerQueryTranslationInfo: VerQueryValue failed\n"));
    }

    return NULL;
}

//////////////////////////////////////////////////////////////////////////////////////////

static LPCTSTR VerQueryStringInfo (LPVOID pVerInfo, DWORD dwXlatInfo, LPCTSTR pszKeyName)
{
    TCHAR szItemName [256];
    LPTSTR pszStringInfo = NULL;
    UINT uInfoLen = 0;

    _sntprintf2 (szItemName, _countof (szItemName) - 1, _T ("\\StringFileInfo\\%04X%04X\\"), LOWORD (dwXlatInfo), HIWORD (dwXlatInfo));

    if (_tcslen (szItemName) + _tcslen (pszKeyName) < _countof (szItemName))
    {
        _tcscat (szItemName, pszKeyName);

        if (VerQueryValue (pVerInfo, szItemName, (LPVOID*) &pszStringInfo, &uInfoLen))
        {
            if (uInfoLen > 0)
            {
                return pszStringInfo;
            }

            Trace (_T ("VerQueryStringInfo: Invalid length of the version-information value: (uInfoLen=%u, pszKeyName=[%s]) uInfoLen should be > 0\n"), uInfoLen, pszKeyName);
        }

        Trace (_T ("VerQueryStringInfo: VerQueryValue failed (pszKeyName=[%s])\n"), pszKeyName);
    }
    else
    {
        Trace (_T ("VerQueryStringInfo: Buffer overflow (length of [%s%s] should be < %u)\n"), szItemName, pszKeyName, _countof (szItemName));
    }

    return NULL;
}

//////////////////////////////////////////////////////////////////////////////////////////

static BOOL CopyQueryStringInfo (LPTSTR pszStringInfoBuffer, size_t nCount, LPVOID pVerInfo, DWORD dwXlatInfo, LPCTSTR pszKeyName)
{
    LPCTSTR pszStringInfo;

    if ((pszStringInfo = VerQueryStringInfo (pVerInfo, dwXlatInfo, pszKeyName)) != NULL)
    {
        lstrcpyn (pszStringInfoBuffer, pszStringInfo, (INT) nCount);
        return TRUE;
    }

    lstrcpyn (pszStringInfoBuffer, pszKeyName, (INT) nCount);
    return FALSE;
}

//////////////////////////////////////////////////////////////////////////////////////////
// 1, 2, 3, 4 becomes 1.2.3.4

VOID AdjustVersionNumber (LPTSTR pszVersionNumber)
{
    TCHAR* pchTarget = pszVersionNumber;
    TCHAR* pchSource = pszVersionNumber;
    TCHAR ch;

    while ((ch = *pchSource++) != _T ('\0'))
    {
        if (ch != _T (' '))
        {
            *pchTarget++ = (TCHAR) ((ch == _T (',')) ? _T ('.') : ch);
        }
    }

    *pchTarget = _T ('\0');
}

//////////////////////////////////////////////////////////////////////////////////////////

static BOOL LoadVersionInfoData (LPVOID pVerInfo)
{
    BOOL bResult = FALSE;
    VS_FIXEDFILEINFO* pFileInfo;
    LPDWORD pdwXlatInfo;
    UINT uXlatCount;

    if ((pFileInfo = VerQueryFixedFileInfo (pVerInfo)) != NULL)
    {
        m_dwProductVersionMS = pFileInfo->dwProductVersionMS;
        m_dwProductVersionLS = pFileInfo->dwProductVersionLS;
        m_dwFileVersionMS = pFileInfo->dwFileVersionMS;
        m_dwFileVersionLS = pFileInfo->dwFileVersionLS;
        m_bIsPreRelease = ((pFileInfo->dwFileFlags & VS_FF_PRERELEASE) != 0);
        m_bIsSpecialBuild = ((pFileInfo->dwFileFlags & VS_FF_SPECIALBUILD) != 0);
        m_bIsPrivateBuild = ((pFileInfo->dwFileFlags & VS_FF_PRIVATEBUILD) != 0);

        if ((pdwXlatInfo = VerQueryTranslationInfo (pVerInfo, &uXlatCount)) != NULL)
        {
            CopyQueryStringInfo (m_sCompanyName, _countof (m_sCompanyName), pVerInfo, pdwXlatInfo [0], _T ("CompanyName"));
            CopyQueryStringInfo (m_sProductName, _countof (m_sProductName), pVerInfo, pdwXlatInfo [0], _T ("ProductName"));
            CopyQueryStringInfo (m_sProductVersion, _countof (m_sProductVersion), pVerInfo, pdwXlatInfo [0], _T ("ProductVersion"));
            CopyQueryStringInfo (m_sLegalCopyright, _countof (m_sLegalCopyright), pVerInfo, pdwXlatInfo [0], _T ("LegalCopyright"));
            CopyQueryStringInfo (m_sLegalTrademarks, _countof (m_sLegalTrademarks), pVerInfo, pdwXlatInfo [0], _T ("LegalTrademarks"));
            CopyQueryStringInfo (m_sComments, _countof (m_sComments), pVerInfo, pdwXlatInfo [0], _T ("Comments"));
            CopyQueryStringInfo (m_sInternalName, _countof (m_sInternalName), pVerInfo, pdwXlatInfo [0], _T ("InternalName"));
            CopyQueryStringInfo (m_sOriginalFilename, _countof (m_sOriginalFilename), pVerInfo, pdwXlatInfo [0], _T ("OriginalFilename"));
            CopyQueryStringInfo (m_sFileDescription, _countof (m_sFileDescription), pVerInfo, pdwXlatInfo [0], _T ("FileDescription"));
            CopyQueryStringInfo (m_sFileVersion, _countof (m_sFileVersion), pVerInfo, pdwXlatInfo [0], _T ("FileVersion"));

            AdjustVersionNumber (m_sProductVersion);
            AdjustVersionNumber (m_sFileVersion);

            if (m_bIsSpecialBuild)
            {
                CopyQueryStringInfo (m_sSpecialBuild, _countof (m_sSpecialBuild), pVerInfo, pdwXlatInfo [0], _T ("SpecialBuild"));
            }
            else
            {
                lstrcpyn (m_sSpecialBuild, _T (""), _countof (m_sSpecialBuild));
            }

            if (m_bIsPrivateBuild)
            {
                CopyQueryStringInfo (m_sPrivateBuild, _countof (m_sPrivateBuild), pVerInfo, pdwXlatInfo [0], _T ("PrivateBuild"));
            }
            else
            {
                lstrcpyn (m_sPrivateBuild, _T (""), _countof (m_sPrivateBuild));
            }

            bResult = TRUE;
        }
    }

    return bResult;
}

//////////////////////////////////////////////////////////////////////////////////////////

static BOOL InternalLoadVersionInfo (VOID)
{
    BOOL bResult = FALSE;
    HMODULE hModule = HINST_THISCOMPONENT;
    HRSRC hRsrc;
    HGLOBAL hVerInfo;
    LPVOID pVerInfo;

    if (Win32Call ((hRsrc = FindResource (hModule, MAKEINTRESOURCE (VS_VERSION_INFO), RT_VERSION)) != NULL, _T ("InternalLoadVersionInfo.FindResource")))
    {
        if (Win32Call ((hVerInfo = LoadResource (hModule, hRsrc)) != NULL, _T ("InternalLoadVersionInfo.LoadResource")))
        {
            if ((pVerInfo = LockResource (hVerInfo)) != NULL)
            {
                if (LoadVersionInfoData (pVerInfo))
                {
                    bResult = TRUE;
                }

                UnlockResource (hVerInfo);
            }
            else
            {
                Trace (_T ("LockResource failed\n"));
            }

            (VOID) FreeResource (hVerInfo);
        }
    }

    return bResult;
}

//////////////////////////////////////////////////////////////////////////////////////////

#if 0

static BOOL InternalLoadVersionInfo (VOID)
{
    BOOL bResult = FALSE;
    DWORD dwVerInfoSize;
    DWORD dwHandle;
    LPVOID pVerInfo;

    if (Win32Call ((dwVerInfoSize = GetFileVersionInfoSize (_tpgmptr, &dwHandle)) > 0, _T ("InternalLoadVersionInfo.GetFileVersionInfoSize")))
    {
        if ((pVerInfo = malloc (dwVerInfoSize)) != NULL)
        {
            if (Win32Call (GetFileVersionInfo (_tpgmptr, dwHandle, dwVerInfoSize, pVerInfo), _T ("InternalLoadVersionInfo.GetFileVersionInfo")))
            {
                if (LoadVersionInfoData (pVerInfo))
                {
                    bResult = TRUE;
                }
            }

            free (pVerInfo);
        }
        else
        {
            TraceWin32Message (ERROR_NOT_ENOUGH_MEMORY, _T ("malloc"));
        }
    }

    return bResult;
}

#endif

//////////////////////////////////////////////////////////////////////////////////////////
// THIS IS NOT MULTI-THREADED!

static BOOL LoadVersionInfo (VOID)
{
    if (! m_IsVersionInfoLoaded)
    {
        m_IsVersionInfoLoaded = InternalLoadVersionInfo ();
    }

    return m_IsVersionInfoLoaded;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT GetVersionInfoProductMajorPart (VOID)
{
    return LoadVersionInfo () ? HIWORD (m_dwProductVersionMS) : -1;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT GetVersionInfoProductMinorPart (VOID)
{
    return LoadVersionInfo () ? LOWORD (m_dwProductVersionMS) : -1;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT GetVersionInfoProductBuildPart (VOID)
{
    return LoadVersionInfo () ? HIWORD (m_dwProductVersionLS) : -1;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT GetVersionInfoProductRevisionPart (VOID)
{
    return LoadVersionInfo () ? LOWORD (m_dwProductVersionLS) : -1;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT GetVersionInfoFileMajorPart (VOID)
{
    return LoadVersionInfo () ? HIWORD (m_dwFileVersionMS) : -1;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT GetVersionInfoFileMinorPart (VOID)
{
    return LoadVersionInfo () ? LOWORD (m_dwFileVersionMS) : -1;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT GetVersionInfoFileBuildPart (VOID)
{
    return LoadVersionInfo () ? HIWORD (m_dwFileVersionLS) : -1;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT GetVersionInfoFileRevisionPart (VOID)
{
    return LoadVersionInfo () ? LOWORD (m_dwFileVersionLS) : -1;
}

//////////////////////////////////////////////////////////////////////////////////////////

BOOL GetVersionInfoIsPreRelease (VOID)
{
    return LoadVersionInfo () ? m_bIsPreRelease : FALSE;
}

//////////////////////////////////////////////////////////////////////////////////////////

BOOL GetVersionInfoIsSpecialBuild (VOID)
{
    return LoadVersionInfo () ? m_bIsSpecialBuild : FALSE;
}

//////////////////////////////////////////////////////////////////////////////////////////

BOOL GetVersionInfoIsPrivateBuild (VOID)
{
    return LoadVersionInfo () ? m_bIsPrivateBuild : FALSE;
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoCompanyName (VOID)
{
    return LoadVersionInfo () ? m_sCompanyName : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoProductName (VOID)
{
    return LoadVersionInfo () ? m_sProductName : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoProductVersion (VOID)
{
    return LoadVersionInfo () ? m_sProductVersion : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoLegalCopyright (VOID)
{
    return LoadVersionInfo () ? m_sLegalCopyright : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoLegalTrademarks (VOID)
{
    return LoadVersionInfo () ? m_sLegalTrademarks : _T ("");
}
//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoComments (VOID)
{
    return LoadVersionInfo () ? m_sComments : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoInternalName (VOID)
{
    return LoadVersionInfo () ? m_sInternalName : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoOriginalFilename (VOID)
{
    return LoadVersionInfo () ? m_sOriginalFilename : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoFileDescription (VOID)
{
    return LoadVersionInfo () ? m_sFileDescription : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoFileVersion (VOID)
{
    return LoadVersionInfo () ? m_sFileVersion : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoSpecialBuild (VOID)
{
    return LoadVersionInfo () ? m_sSpecialBuild : _T ("");
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetVersionInfoPrivateBuild (VOID)
{
    return LoadVersionInfo () ? m_sPrivateBuild : _T ("");
}
